package org.jackysoft.service.impl;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.criterion.Property;
import org.hibernate.jdbc.Work;
import org.jackysoft.entity.CurrentPrevStep;
import org.jackysoft.entity.FieldAccess;
import org.jackysoft.entity.FieldAccessPK;
import org.jackysoft.entity.HibernateCurrentBackStep;
import org.jackysoft.entity.HibernateCurrentStep;
import org.jackysoft.entity.HibernateHistoryStep;
import org.jackysoft.entity.HibernateStep;
import org.jackysoft.entity.HibernateWorkflowEntry;
import org.jackysoft.entity.OfficeDocument;
import org.jackysoft.entity.Step;
import org.jackysoft.entity.User;
import org.jackysoft.entity.WorkflowEntry;
import org.jackysoft.opensymphony.workflow.entity.ActionDescriptor;
import org.jackysoft.opensymphony.workflow.entity.ConditionDescriptor;
import org.jackysoft.opensymphony.workflow.entity.ConditionalResultDescriptor;
import org.jackysoft.opensymphony.workflow.entity.ConditionsDescriptor;
import org.jackysoft.opensymphony.workflow.entity.FunctionDescriptor;
import org.jackysoft.opensymphony.workflow.entity.InvalidWorkflowDescriptorException;
import org.jackysoft.opensymphony.workflow.entity.JoinDescriptor;
import org.jackysoft.opensymphony.workflow.entity.PermissionDescriptor;
import org.jackysoft.opensymphony.workflow.entity.RegisterDescriptor;
import org.jackysoft.opensymphony.workflow.entity.RestrictionDescriptor;
import org.jackysoft.opensymphony.workflow.entity.ResultDescriptor;
import org.jackysoft.opensymphony.workflow.entity.SplitDescriptor;
import org.jackysoft.opensymphony.workflow.entity.StepDescriptor;
import org.jackysoft.opensymphony.workflow.entity.ValidatorDescriptor;
import org.jackysoft.opensymphony.workflow.entity.WorkflowDescriptor;
import org.jackysoft.opensymphony.workflow.entity.WorkflowName;
import org.jackysoft.service.AbstractIdentifiedLongService;
import org.jackysoft.service.FieldAccessService;
import org.jackysoft.service.OfficeDocumentService;
import org.jackysoft.service.VariableService;
import org.jackysoft.service.WorkflowService;
import org.jackysoft.util.AttachmentType;
import org.jackysoft.util.SOAOfficeUtil;
import org.jackysoft.util.WorkflowConstant;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
@Transactional
public class WorkflowServiceImpl 
extends AbstractIdentifiedLongService<WorkflowEntry>
		implements WorkflowService {
	/**
	 * 
	 */
	private static final long serialVersionUID = 6616739567385904077L;
	private boolean validate = false;
	private static boolean forceReload;
	// ~ Instance fields
	// ////////////////////////////////////////////////////////
	private static final Log log = LogFactory.getLog(WorkflowServiceImpl.class);
	private String cacheRegion = null;
	private boolean cacheable = false;
	private Map<String, WorkflowDescriptor> workflows;
	private OfficeDocumentService officedocService;
	private FieldAccessService fieldAccessService;

	// ~ Methods
	// ////////////////////////////////////////////////////////////////

	// ~ Getter/Setter
	// ////////////////////////////////////////////////////////////////

	public void setCacheRegion(String cacheRegion) {
		this.cacheRegion = cacheRegion;
	}

	public WorkflowServiceImpl() {
		this.type = WorkflowEntry.class;
	}

	public void setCacheable(boolean cacheable) {
		this.cacheable = cacheable;
	}

	public void setEntryState(final long entryId, final int state)
			throws DataAccessException {
		final HibernateWorkflowEntry entry = loadEntry(entryId);
		entry.setState(state);
		update(entry);
	}

	@Transactional
	public Step createCurrentStep(final long entryId, final int stepId,
			int actionId,
			final String owner, final Date startDate, final Date dueDate,
			final String status, final long[] previousIds)
			throws DataAccessException {

		final HibernateWorkflowEntry entry = loadEntry(entryId);
		final HibernateCurrentStep step = new HibernateCurrentStep();
       
		step.setStepId(stepId);
		step.setActionId(actionId);
		step.setOwner(owner);
		step.setCaller(owner);
		step.setStartDate(startDate);
		step.setDueDate(dueDate);
		step.setStatus(status);		
		for (int i = 0; i < previousIds.length; i++) {
			step.getPreviousSteps().add(previousIds[i]);
		}
		entry.addCurrentSteps(step);
		// createBean(step);

		return step;
	}

	@Transactional
	public WorkflowEntry createEntry(String workflowName)
			throws DataAccessException {
		final HibernateWorkflowEntry entry = new HibernateWorkflowEntry();
		entry.setState(WorkflowEntry.CREATED);
		entry.setWorkflowName(workflowName);
		create(entry);
		return entry;
	}

	public List findCurrentSteps(final long entryId) throws DataAccessException {
		// We are asking for current step list, so here we have an anti-lazy
		// copy of the Hibernate array in memory. This also prevents problem
		// in case the use is going with a pattern that span a session
		// for method call
		return new ArrayList(loadEntry(entryId).getCurrentSteps());
	}

	public WorkflowEntry findEntry(long entryId) throws DataAccessException {
		return loadEntry(entryId);
	}

	public List findHistorySteps(final long entryId) throws DataAccessException {
		// We are asking for history step list, so here we have an anti-lazy
		// copy of the Hibernate array in memory. This also prevents problem
		// in case the use is going with a pattern that span a session
		// for method call
		return new ArrayList(loadEntry(entryId).getHistorySteps());
	}

	public Step markFinished(Step step, int actionId, Date finishDate,
			String status, String caller) throws DataAccessException {
		final HibernateCurrentStep currentStep = (HibernateCurrentStep) step;

		currentStep.setActionId(actionId);
		currentStep.setFinishDate(finishDate);
		currentStep.setStatus(status);
		currentStep.setCaller(caller);

		return currentStep;
	}

	@Transactional
	public void moveToHistory(final Step step) throws DataAccessException {

		final HibernateCurrentStep currentStep = (HibernateCurrentStep) step;
		final HibernateWorkflowEntry entry = currentStep.getEntry();
		final HibernateHistoryStep hStep = new HibernateHistoryStep(currentStep);
		hStep.setId(currentStep.getId());
		entry.removeCurrentSteps(currentStep);
		removeCurrentStep(currentStep);
		entry.addHistorySteps(hStep);
		// updateBean(hStep);
		log.info("移动步骤   " + step.getId() + " 到历史步骤    owner: "
				+ step.getOwner());

	}

	protected String getCacheRegion() {
		return cacheRegion;
	}

	protected boolean isCacheable() {
		return cacheable;
	}

	// ~ DAO Methods
	// ////////////////////////////////////////////////////////////////
	protected HibernateWorkflowEntry loadEntry(final long entryId)
			throws DataAccessException {
		Session session = getSession();
		HibernateWorkflowEntry entry = (HibernateWorkflowEntry) session.load(
				HibernateWorkflowEntry.class, new Long(entryId));
		Hibernate.initialize(entry);
		Hibernate.initialize(entry.getCurrentSteps());
		Hibernate.initialize(entry.getHistorySteps());
		return entry;
	}

	public void reloadAll() {
		this.initilize();
	}

	// ~ Inner Interfaces
	// ///////////////////////////////////////////////////////

	// //////////////////////////////////////////////////////////////////
	// / 自定义方法
	// /
	// //////////////////////////////////////////////////////////////////
	@Override
	public void completeWorkflow(final long id) {

	}

	@Override
	public void deleteWorkflow(final long id) {

	}

	@Override
	public User getActor(final String name) {
		return (User) getSession().get(User.class, name);

	}

	@Override
	@Transactional
	public void rollbackWorkflow(final long id) {
		Session session = getSession();
		HibernateWorkflowEntry entry = (HibernateWorkflowEntry) session.get(
				HibernateWorkflowEntry.class, id);
		List<HibernateCurrentStep> currentSteps = entry.getCurrentSteps();
		boolean firstMove = true;
		for (Iterator<HibernateCurrentStep> itr = currentSteps.iterator(); itr
				.hasNext();) {
			HibernateCurrentStep theStep = itr.next();
			List<CurrentPrevStep> previouSteps = session.createCriteria(CurrentPrevStep.class).add(Property.forName("id").eq(theStep.getId())).list();
			if (firstMove) {
				for (Iterator<CurrentPrevStep> citr = previouSteps.iterator(); citr
						.hasNext();) {
					CurrentPrevStep cprev = citr.next();
					moveHistoryToCurrent(cprev.getPreviousId(), session);
				}
			}
			firstMove = false;
			itr.remove();
			removeCurrentStep(entry,theStep,session);
			

		}
	}

	protected void moveHistoryToCurrent(Long id, Session session) {
		HibernateHistoryStep hisprev = (HibernateHistoryStep) session.get(
				HibernateHistoryStep.class, id);
		hisprev.setActionId(-1);
		hisprev.setFinishDate(null);
		HibernateCurrentBackStep curstep = new HibernateCurrentBackStep(hisprev);
		curstep.setId(hisprev.getId());
		curstep.setStatus(WorkflowConstant.STATE_UNDERWAY.toString());
		session.save(curstep);
		session.delete(hisprev);
	}

	protected void removeCurrentStep(HibernateWorkflowEntry entry ,HibernateCurrentStep step,Session session){
		
		entry.getCurrentSteps().remove(step);
		//session.delete(step);
	}
	

	@Override
	public void modifyWorkflow(final HibernateWorkflowEntry entry) {
		Session session = getSession();
		session.saveOrUpdate(entry);

	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public void init() throws DataAccessException {

		Session session = null;
		try {
			workflows = new HashMap();
			session = getSession();
			String hql = "from " + WorkflowName.class.getName();
			List workflowNames = session.createQuery(hql).list();
			for (Iterator iter = workflowNames.iterator(); iter.hasNext();) {
				WorkflowName wfn = (WorkflowName) iter.next();
				WorkflowDescriptor des = wfn.getWorkflowDescriptor();
				loadWorkflowDescriptor(des);
				if (validate) {
					des.validate();
				}
				workflows.put(wfn.getWorkflowName(), des);
			}
		} catch (Exception e) {
			log.error(e);
			e.printStackTrace();
		} finally {
			if (session != null)
				session.close();
		}

	}

	@PostConstruct
	public void initilize() {
		init();
	}

	

	/**
	 * @param name
	 *            工作流名
	 * */
	@Override
	public WorkflowDescriptor getWorkflowDescriptor(String name) {
		if (name == null)
			throw new RuntimeException("name can not be null");
		WorkflowDescriptor wdes = (WorkflowDescriptor) workflows.get(name);
		if (wdes == null) {
			try {
				loadWorkflow(name, false);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return wdes;
	}

	@Override
	public void setForceReload(boolean load) {
		WorkflowServiceImpl.forceReload = load;

	}

	@Override
	public void setReload(boolean reload) {

	}

	@Override
	public void setValidate(boolean validate) {
		this.validate = validate;

	}

	// /////////////////////////////////////////////////////////////////
	// //////methodsto load workflow
	// //////
	// ////////////////////////////////////////////////

	private synchronized void loadWorkflow(final String workflowName,
			final boolean validate) throws Exception {
		Session session = getSession();
		try {

			WorkflowName wfn = (WorkflowName) session.get(WorkflowName.class,
					workflowName);
			if(wfn==null) return;
			WorkflowDescriptor des = wfn.getWorkflowDescriptor();
			loadWorkflowDescriptor(des);
			if (validate) {
				wfn.getWorkflowDescriptor().validate();
			}
			workflows.put(wfn.getWorkflowName(), des);
		} catch (InvalidWorkflowDescriptorException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public String[] getWorkflowNames() {
		String[] names = null;
		if (workflows != null && !workflows.isEmpty()) {
			names = new String[workflows.size()];
			workflows.keySet().toArray(names);
		}
		return names;
	}

	void loadWorkflowDescriptor(WorkflowDescriptor des) {
		new WorkflowTools().loadWorkflowDescriptor(des);

	}

	
	public static boolean isForceReload() {
		return forceReload;
	}

	@Override
	public boolean stepInStatus(int stepId, String status)
			throws DataAccessException {
		// TODO 获取某个步骤
		Session session = getSession();
		HibernateStep cs = (HibernateCurrentStep) session.get(
				HibernateCurrentStep.class, stepId);
		if (cs != null) {
			return status.equalsIgnoreCase(cs.getStatus());
		}
		cs = (HibernateHistoryStep) session.get(HibernateHistoryStep.class,
				stepId);

		if (cs != null)
			return status.equalsIgnoreCase(cs.getStatus());

		throw new IllegalStateException("步骤[" + stepId + "] 不存在 ");
	}

	@Override
	public Map<String, WorkflowDescriptor> getWorkflowCaches() {
		return this.workflows;
	}

	@Override
	public HibernateHistoryStep loadHistoryStep(Long id)
			throws DataAccessException {
		Object obj = getSession().get(HibernateHistoryStep.class, id);
		return (HibernateHistoryStep) (obj);
	}

	@Override
	public HibernateCurrentStep loadCurrentStep(Long id)
			throws DataAccessException {
		Object obj = getSession().get(HibernateCurrentStep.class, id);
		return (HibernateCurrentStep) (obj);

	}


	@Override
	public void removeCurrentStep(HibernateCurrentStep step) {
		Session session = getSession();
		session.delete(step);		
	}

	@Override
	public void updateDocument(byte[] data,long workflowId) throws DataAccessException {
		final HibernateWorkflowEntry entry = (HibernateWorkflowEntry) findEntry(workflowId);
		OfficeDocument oti = entry.getOfficeDocument() == null ? new OfficeDocument():entry.getOfficeDocument();
		
		oti.setData(data);
		oti.setType(AttachmentType.WORD);
		oti.setExt("doc");
		if(entry.getOfficeDocument()==null){
			entry.setOfficeDocument(oti);
		}
		
	}

	@Override
	public OfficeDocument getOffice(long workflowId) throws DataAccessException {
		final HibernateWorkflowEntry entry = (HibernateWorkflowEntry)findEntry(workflowId);
		OfficeDocument oti = entry.getOfficeDocument();
		return oti;
	}

	@Override
	public void collate(byte[] data,String ext, long workflowId) throws DataAccessException {
		
		final HibernateWorkflowEntry entry = (HibernateWorkflowEntry) findEntry(workflowId);
		OfficeDocument oti = entry.getOfficeDocument() == null ? new OfficeDocument()
				: entry.getOfficeDocument();
		oti.setCollateData(data);
		oti.setType(AttachmentType.getType(ext));		
		oti.setExt(ext);
		entry.setOfficeDocument(oti);
		officedocService.saveOrUpdate(oti);
		create(entry);		
		
	}

	
	
	@Override
	public FieldAccess getFieldAccess(long workflowId, int stepId) {
		WorkflowEntry we =  findEntry(workflowId);
		if(we==null)
			throw new NullPointerException(String.format("workflow entity not found with id %d", workflowId));
		String workflowName = we.getWorkflowName();
		FieldAccessPK fid = new FieldAccessPK(workflowName,new Integer(stepId).longValue());
		WorkflowDescriptor wdesc = getWorkflowDescriptor(workflowName);
		StepDescriptor step = wdesc.getStep(stepId);
		fid.setStepName(step.getName());
		final FieldAccess stepInfo = fieldAccessService.get(fid);
		return stepInfo;
	}

	@Resource
	public void setFieldAccessService(FieldAccessService fieldAccessService) {
		this.fieldAccessService = fieldAccessService;
	}

	@Override
	public void setWorkflowVariableService(
			VariableService workflowVariableService) {
		// TODO Auto-generated method stub
		
	}

	@Resource
	public void setOfficedocService(OfficeDocumentService officedocService) {
		this.officedocService = officedocService;
	}

	

}

class WorkflowWork implements Work {
	private String sql;
	private Long id;

	public WorkflowWork(String sql, Long id) {
		this.sql = sql;
		this.id = id;
	}

	@Override
	public void execute(Connection connection) throws SQLException {
		CallableStatement cstmt = connection.prepareCall(sql);
		cstmt.setLong(1, id);
		cstmt.executeUpdate();
		cstmt.close();
	}

}

class WorkflowTools {
	public void loadWorkflowDescriptor(WorkflowDescriptor des) {
		if (des != null) {
			if (des.getGlobalActions() != null)
				loadActionDescriptors(des.getGlobalActions());
			// initialactions
			if (des.getInitialActions() != null)
				loadActionDescriptors(des.getInitialActions());
			// common actions
			if (des.getCommonActions() != null)
				loadActionDescriptors(des.getCommonActions().entrySet());
			// joins
			if (des.getJoins() != null)
				loadJoinDescritors(des.getJoins());
			if (des.getMetaAttributes() != null)
				des.getMetaAttributes().size();
			if (des.getRegisters() != null)
				loadRegisterDescriptors(des.getRegisters());
			if (des.getSplits() != null)
				loadSplitDescriptors(des.getSplits());
			if (des.getSteps() != null)
				loadStepDescriptors(des.getSteps());
			if (des.getGlobalConditions() != null)
				loadConditionsDescriptor(des.getGlobalConditions());
			if (des.getTriggerFunctions() != null)
				loadFunctionDescriptors(des.getTriggerFunctions().entrySet());
		}
	}

	private void loadActionDescriptor(ActionDescriptor act) {
		if (act != null) {

			//
			if (act.getConditionalResults() != null)
				for (Object crd : act.getConditionalResults()) {
					loadConditionalResultDescriptor((ConditionalResultDescriptor) crd);
				}
			// meta attrs
			if (act.getMetaAttributes() != null)
				act.getMetaAttributes().size();
			// post functions
			if (act.getPostFunctions() != null)
				loadFunctionDescriptors(act.getPostFunctions());
			if (act.getPreFunctions() != null)
				loadFunctionDescriptors(act.getPreFunctions());
			//
			if (act.getRestriction() != null)
				loadRestrictionDescriptor(act.getRestriction());
			// lazy UnconditionalResult
			if (act.getUnconditionalResult() != null)
				loadResultDescriptor(act.getUnconditionalResult());
			// end of UnconditionalResult

			// act.getValidators()
			if (act.getValidators() != null)
				loadValidatorDescriptors(act.getValidators());
		}
	}

	// load actiondescriptor operator
	private void loadActionDescriptors(Collection actions) {
		if (actions != null)
			for (Object gact : actions) {
				loadActionDescriptor((ActionDescriptor) gact);
			}
	}

	// load result
	private void loadResultDescriptor(ResultDescriptor rs) {
		if (rs != null) {
			loadFunctionDescriptors(rs.getPostFunctions());
			loadFunctionDescriptors(rs.getPreFunctions());
			loadValidatorDescriptors(rs.getValidators());
		}
	}

	private void loadResultDescriptors(Collection rds) {
		if (rds != null)
			for (Object obj : rds) {
				loadResultDescriptor((ResultDescriptor) obj);
			}
	}

	private void loadConditionalResultDescriptor(ConditionalResultDescriptor rs) {
		if (rs != null) {
			if (rs.getConditions() != null)
				for (Object obj : rs.getConditions()) {					
					ConditionDescriptor cd = (ConditionDescriptor) obj;
					if (cd.getArgs() != null)cd.getArgs().size();
				}
			if (rs.getPostFunctions() != null)
				loadFunctionDescriptors(rs.getPostFunctions());
			if (rs.getPreFunctions() != null)
				loadFunctionDescriptors(rs.getPreFunctions());
			if (rs.getValidators() != null)
				loadValidatorDescriptors(rs.getValidators());
		}
	}

	private void loadConditionsDescriptor(ConditionsDescriptor cds) {
		if (cds != null) {
			if (cds.getConditions() != null)
				for (Object obj : cds.getConditions()) {
					ConditionDescriptor cd = (ConditionDescriptor) obj;
					if (cd.getArgs() != null)
						cd.getArgs().size();

				}
		}
	}

	private void loadJoinDescritors(Collection jds) {
		if (jds != null)
			for (Object obj : jds) {
				loadJoinDescriptor((JoinDescriptor) obj);

			}
	}

	private void loadJoinDescriptor(JoinDescriptor jd) {
		if (jd != null) {
			if (jd.getConditions() != null)
				for (Object crd : jd.getConditions()) {
					loadConditionsDescriptor((ConditionsDescriptor) crd);
				}
			loadResultDescriptor(jd.getResult());
		}
	}

	private void loadRegisterDescriptors(Collection rds) {
		if (rds != null)
			for (Object obj : rds) {
				RegisterDescriptor rd = (RegisterDescriptor) obj;
				if (rd.getArgs() != null)
					rd.getArgs().size();
			}
	}

	private void loadSplitDescriptors(Collection sps) {
		if (sps != null)
			for (Object obj : sps) {
				SplitDescriptor sd = (SplitDescriptor) obj;
				if (sd != null && sd.getResults() != null)
					loadResultDescriptors(sd.getResults());
			}
	}

	private void loadStepDescriptors(Collection sds) {
		if (sds != null)
			for (Object obj : sds)
				loadStepDescriptor((StepDescriptor) obj);
	}

	private void loadStepDescriptor(StepDescriptor sd) {
		if (sd != null) {
			if (sd.getActions() != null)
				loadActionDescriptors(sd.getActions());
			if (sd.getPostFunctions() != null)
				loadFunctionDescriptors(sd.getPostFunctions());
			if (sd.getPreFunctions() != null)
				loadFunctionDescriptors(sd.getPreFunctions());
			if (sd.getCommonActions() != null)
				loadActionDescriptors(sd.getCommonActions());
			if (sd.getMetaAttributes() != null)
				sd.getMetaAttributes().size();
			if (sd.getPermissions() != null)
				loadPermissionDescriptors(sd.getPermissions());
		}
	}

	private void loadPermissionDescriptors(Collection pds) {
		if (pds != null)
			for (Object obj : pds)
				loadPermissionDescriptor((PermissionDescriptor) obj);
	}

	private void loadPermissionDescriptor(PermissionDescriptor pd) {
		if (pd != null && pd.getRestriction() != null)
			loadRestrictionDescriptor(pd.getRestriction());
	}

	private void loadRestrictionDescriptor(RestrictionDescriptor rd) {
		if (rd != null && rd.getConditionsDescriptor() != null)
			loadConditionsDescriptor(rd.getConditionsDescriptor());
	}

	private void loadFunctionDescriptors(Collection fds) {
		if (fds != null)
			for (Object obj : fds)
				loadFunctionDescriptor((FunctionDescriptor) obj);
	}

	private void loadFunctionDescriptor(FunctionDescriptor fd) {
		if (fd != null && fd.getArgs() != null) {
			if (fd.getArgs() != null)
				fd.getArgs().size();
		}
	}

	private void loadValidatorDescriptors(Collection vds) {
		if (vds != null)
			for (Object obj : vds)
				loadValidatorDescriptor((ValidatorDescriptor) obj);
	}

	private void loadValidatorDescriptor(ValidatorDescriptor vd) {
		if (vd != null && vd.getArgs() != null) {
			if (vd.getArgs() != null)
				vd.getArgs().size();
		}
	}

}
